Skip to content

Conversation

@jakevollkommer
Copy link

Closes #2634

✅ Checklist

  • I have followed every step in the contributing guide
  • The PR title follows the convention.
  • I ran and tested the code works

Summary

Adds a custom transport implementation for AI SDK's useChat hook that integrates with Trigger.dev background tasks. This enables long-running AI conversations by triggering tasks, subscribing to realtime run updates via Electric SQL, and streaming AI responses via Server-Sent Events (SSE).

Changes

  • New Hook: useTriggerChat - A drop-in replacement for AI SDK's useChat that works with Trigger.dev tasks
  • Transport Class: TriggerChatTransport - Custom transport implementation following AI SDK's transport pattern
  • Dependencies: Added ai (^5.0.82), @ai-sdk/react (^2.0.14), and eventsource-parser (^3.0.0)

Technical Details

Architecture

Developer Requirements

Important: Developers must create their own server action to trigger tasks. Since useTriggerChat is a client-side hook, it cannot directly call tasks.trigger() (which requires server-side execution). The triggerTask option expects a server action that:

  1. Accepts the task identifier and payload
  2. Calls tasks.trigger() on the server
  3. Returns { success: true, runId, publicAccessToken }

Error Handling

  • Gracefully handles stream disconnections and abort signals
  • Warns on unparseable SSE chunks without breaking the stream
  • Only closes controller when run finishes (not when individual streams end)

Testing

Manually tested with a local project using pnpm patch to verify:

  • ✅ Task triggering and run creation
  • ✅ Realtime run status updates
  • ✅ SSE streaming of AI responses
  • ✅ Multiple concurrent streams
  • ✅ Graceful handling of stream completion
  • ✅ TypeScript compilation

Usage Example

1. Define your Trigger.dev task (e.g. src/trigger/chat.ts):

import { metadata, task } from "@trigger.dev/sdk/v3";
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";

export const chatTask = task({
  id: "chat",
  run: async ({ messages }: { messages: Array<{ role: string; content: string }> }) => {
    const result = streamText({
      model: openai("gpt-5"),
      messages,
    });

    // CRITICAL: Stream the result to the client using metadata.stream()
    // The stream key MUST match the streamKey option in useTriggerChat (default: "chat")
    await metadata.stream("chat", result.toUIMessageStream());

    const text = await result.text;
    return { text };
  },
});

2. Create a server action (e.g. src/actions.ts):

"use server";

import { tasks } from "@trigger.dev/sdk/v3";

export async function triggerChatTask(task: string, payload: unknown) {
  const handle = await tasks.trigger(task, payload);
  return {
    success: true,
    runId: handle.id,
    publicAccessToken: handle.publicAccessToken,
  };
}

3. Use the hook in your component (e.g. src/components/Chat.tsx):

"use client";

import { useTriggerChat } from "@trigger.dev/react-hooks";
import { chatTask } from "../trigger/chat";
import { triggerChatTask } from "../actions";

export function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useTriggerChat({
    triggerTask: triggerChatTask,
  });

  return (
    <form onSubmit={handleSubmit}>
      <div>
        {messages.map((msg) => (
          <div key={msg.id}>
            <strong>{msg.role}:</strong> {msg.content}
          </div>
        ))}
      </div>
      <input value={input} onChange={handleInputChange} />
      <button type="submit">Send</button>
    </form>
  );
}

Changelog

Added useTriggerChat hook to @trigger.dev/react-hooks that provides AI SDK useChat integration with Trigger.dev background tasks. Enables long-running AI conversations with realtime streaming via custom transport implementation.

New exports:

  • useTriggerChat - Hook for AI chat integration
  • TriggerChatTransport - Custom transport class
  • TriggerChatTransportOptions - Transport configuration type
  • TriggerChatTaskPayload - Task payload type

New dependencies:

  • ai@^5.0.82
  • @ai-sdk/react@^2.0.14
  • eventsource-parser@^3.0.0

Resources


Screenshots

N/A - This is a developer-facing hook with no UI

💯

@changeset-bot
Copy link

changeset-bot bot commented Oct 29, 2025

🦋 Changeset detected

Latest commit: 136085f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@trigger.dev/react-hooks Minor
d3-chat Patch
references-d3-openai-agents Patch
references-nextjs-realtime Patch
@trigger.dev/build Minor
@trigger.dev/core Minor
@trigger.dev/python Minor
@trigger.dev/redis-worker Minor
@trigger.dev/rsc Minor
@trigger.dev/schema-to-json Minor
@trigger.dev/sdk Minor
@trigger.dev/database Minor
@trigger.dev/otlp-importer Minor
trigger.dev Minor
@internal/cache Patch
@internal/clickhouse Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/zod-worker Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

Walkthrough

Adds a new useTriggerChat React hook and TriggerChatTransport implementation to enable Trigger.dev–backed streaming chat for the AI SDK. Introduces types, helpers (metadata parsing, SSE stream subscription, reconnection), and runtime peer-dependency checks. Exports the hook from the package index and updates packages/react-hooks/package.json to declare optional peer dependencies for "@ai-sdk/react", "@electric-sql/client", "ai", and "eventsource-parser". Includes a changelog entry marking a minor release. No other public API declarations were removed or altered.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas to focus on:
    • packages/react-hooks/src/hooks/useTriggerChat.ts (TriggerChatTransport lifecycle, SSE parsing, stream enqueueing, abort/cleanup, reconnection logic)
    • Runtime peer-dependency check and error paths
    • package.json peerDependencies/peerDependenciesMeta correctness and optional semantics
    • packages/react-hooks/src/index.ts export addition
    • Changelog entry (.changeset) for accuracy and metadata consistency

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "feat(react-hooks): add TriggerChatTransport for AI SDK useChat" clearly and specifically describes the primary change in this changeset. It uses the conventional commit format with scope (react-hooks) and accurately summarizes the main additions: a transport class and integration for AI SDK's useChat hook. The title is concise, specific enough for colleagues scanning history to understand the change, and directly reflects the implementation shown in the raw summary.
Linked Issues Check ✅ Passed The code changes fully address the objectives from linked issue #2634. The implementation delivers all key requirements: a custom transport class (TriggerChatTransport) that implements the AI SDK v5 transport pattern for useChat integration, a useTriggerChat hook that serves as a drop-in replacement, the ability to trigger background tasks via the transportOptions, subscription to task metadata streams for receiving streamed AI responses through SSE, and integration with Trigger.dev's realtime features using Electric SQL ShapeStream for run status updates. The PR description provides concrete examples demonstrating how developers can implement the required server action, define tasks with metadata streaming, and use the hook in client components.
Out of Scope Changes Check ✅ Passed All changes in this pull request are directly scoped to implementing the TriggerChatTransport and useTriggerChat hook feature as defined in issue #2634. The changelog entry, peer dependency additions, new useTriggerChat.ts hook implementation, and index.ts export are all necessary and directly related to enabling AI SDK useChat integration with Trigger.dev. No extraneous refactoring, unrelated fixes, or out-of-scope modifications are present in the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines +30 to +43
* @remarks
* **CRITICAL:** Your Trigger.dev task MUST call `metadata.stream()` with the AI SDK stream.
* The stream key used in `metadata.stream()` must match the `streamKey` option (default: "chat").
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there may be a better place to put this piece of documentation

the tricky bit here is there are 2 external files required to enable this hook

  1. the trigger.dev task definition itself, which must call a stream() method from ai and forward the stream to metadata.stream()
  2. the server action which invoked the trigger.dev task server-side

Comment on lines 34 to 64
* @example Trigger.dev task that streams AI responses:
* ```ts
* import { metadata, task } from "@trigger.dev/sdk/v3";
* import { streamText } from "ai";
* import { openai } from "@ai-sdk/openai";
*
* export const chatTask = task({
* id: "chat",
* run: async ({ messages }) => {
* const result = streamText({
* model: openai("gpt-4"),
* messages,
* });
*
* // CRITICAL: Stream to client using metadata.stream()
* await metadata.stream("chat", result.toUIMessageStream());
*
* return { text: await result.text };
* },
* });
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to the above, it's a bit awkward adding this example here, because the task parameter actually just needs the string which is the id of the task ("chat" in the above example)

still unsure if this is the best way to document this

Comment on lines 331 to 362
function parseMetadata(
metadata: Record<string, unknown> | string | undefined
): ParsedMetadata | undefined {
if (!metadata) return undefined;

if (typeof metadata === "string") {
try {
return JSON.parse(metadata) as ParsedMetadata;
} catch {
return undefined;
}
}

if (typeof metadata === "object") {
return metadata as ParsedMetadata;
}

return undefined;
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normally I would use zod for this, but I think this matches patterns in other hooks

Comment on lines 481 to 573
* @remarks
* **CRITICAL SETUP REQUIREMENTS:**
*
* 1. Your Trigger.dev task MUST call `metadata.stream()` to stream responses:
* ```ts
* await metadata.stream("chat", result.toUIMessageStream());
* ```
*
* 2. You must provide a server action that calls `tasks.trigger()`:
* ```ts
* "use server";
* export async function triggerChat(task: string, payload: unknown) {
* const handle = await tasks.trigger(task, payload);
* return { success: true, runId: handle.id, publicAccessToken: handle.publicAccessToken };
* }
* ```
*
* @example Complete setup with three files:
*
* **1. Trigger.dev task (src/trigger/chat.ts):**
* ```ts
* import { metadata, task } from "@trigger.dev/sdk/v3";
* import { streamText } from "ai";
* import { openai } from "@ai-sdk/openai";
*
* export const chatTask = task({
* id: "chat",
* run: async ({ messages }) => {
* const result = streamText({ model: openai("gpt-4"), messages });
* // CRITICAL: Stream to client
* await metadata.stream("chat", result.toUIMessageStream());
* return { text: await result.text };
* },
* });
* ```
*
* **2. Server action (src/actions.ts):**
* ```ts
* "use server";
* import { tasks } from "@trigger.dev/sdk/v3";
*
* export async function triggerChat(task: string, payload: unknown) {
* const handle = await tasks.trigger(task, payload);
* return { success: true, runId: handle.id, publicAccessToken: handle.publicAccessToken };
* }
* ```
*
* **3. Client component (src/components/Chat.tsx):**
* ```ts
* "use client";
* import { useTriggerChat } from "@trigger.dev/react-hooks";
* import { triggerChat } from "../actions";
*
* export function Chat() {
* const { messages, input, handleInputChange, handleSubmit } = useTriggerChat({
* transportOptions: { triggerTask: triggerChat }
* });
*
* return (
* <form onSubmit={handleSubmit}>
* {messages.map(m => <div key={m.id}>{m.role}: {m.content}</div>)}
* <input value={input} onChange={handleInputChange} />
* <button type="submit">Send</button>
* </form>
* );
* }
* ```
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the above comments apply here as well

…ok with a custom transport for trigger.tdev tasks
@jakevollkommer jakevollkommer marked this pull request as ready for review October 31, 2025 19:18
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27376df and ba97cb0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • .changeset/thirty-olives-confess.md (1 hunks)
  • packages/react-hooks/package.json (1 hunks)
  • packages/react-hooks/src/hooks/useTriggerChat.ts (1 hunks)
  • packages/react-hooks/src/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/react-hooks/src/index.ts
  • packages/react-hooks/src/hooks/useTriggerChat.ts
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities

Applied to files:

  • packages/react-hooks/src/index.ts
  • .changeset/thirty-olives-confess.md
  • packages/react-hooks/package.json
  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-29T10:06:49.293Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-08-29T10:06:49.293Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from trigger.dev/core in the webapp, never import the root package path; always use one of the documented subpath exports from trigger.dev/core’s package.json

Applied to files:

  • packages/react-hooks/src/index.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use triggerAndWait() only from within a task context (not from generic app code) and handle result.ok or use unwrap() with error handling

Applied to files:

  • packages/react-hooks/src/index.ts
  • .changeset/thirty-olives-confess.md
  • packages/react-hooks/package.json
  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export every task (including subtasks) defined with task(), schedules.task(), or schemaTask()

Applied to files:

  • packages/react-hooks/src/index.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schedules.task(...) for scheduled (cron) tasks; do not implement schedules as plain task() with external cron logic

Applied to files:

  • packages/react-hooks/src/index.ts
  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Define tasks using task({ id, run, ... }) with a unique id per project

Applied to files:

  • packages/react-hooks/src/index.ts
  • .changeset/thirty-olives-confess.md
  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schemaTask({ schema, run, ... }) to validate payloads when input validation is required

Applied to files:

  • packages/react-hooks/src/index.ts
  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Do not use client.defineJob or any deprecated v2 patterns (e.g., eventTrigger) when defining tasks

Applied to files:

  • packages/react-hooks/src/index.ts
  • .changeset/thirty-olives-confess.md
  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata API (metadata.current/get/set/append/stream, etc.) only inside run functions or lifecycle hooks

Applied to files:

  • packages/react-hooks/src/index.ts
  • packages/react-hooks/package.json
  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: For Realtime subscriptions or React hooks, provide a Public Access Token and scope it appropriately (e.g., via TriggerAuthContext)

Applied to files:

  • packages/react-hooks/src/index.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to trigger.config.ts : Configure global task lifecycle hooks (onStart/onSuccess/onFailure) only within trigger.config.ts if needed, not within arbitrary files

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : For idempotent child-task invocations, create and pass idempotencyKey (and optional TTL) when calling trigger()/batchTrigger() from tasks

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When triggering a task multiple times in a loop from inside another task, use batchTrigger()/batchTriggerAndWait() instead of per-item trigger() calls

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-07-18T17:49:24.468Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T17:49:24.468Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : We use zod a lot in packages/core and in the webapp

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
🧬 Code graph analysis (1)
packages/react-hooks/src/hooks/useTriggerChat.ts (2)
packages/core/src/v3/apiClientManager/index.ts (2)
  • accessToken (37-45)
  • baseURL (32-35)
references/d3-openai-agents/src/components/main-app.tsx (1)
  • useChat (20-86)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/react-hooks/src/hooks/useTriggerChat.ts (1)

268-269: Consider making API versions configurable.

The hardcoded version headers may need updates as the Trigger.dev API evolves. Consider adding optional configuration for these versions in TriggerChatTransportOptions.

 export interface TriggerChatTransportOptions<
   TPayload extends TriggerChatTaskPayload = TriggerChatTaskPayload,
 > {
   // ... existing options ...
   
+  /**
+   * Override Electric SQL version header
+   * @default "1.0.0-beta.1"
+   */
+  electricVersion?: string;
+  
+  /**
+   * Override Trigger.dev API version header
+   * @default "2024-11-28"
+   */
+  apiVersion?: string;
 }

Then use them in the ShapeStream constructor:

 const runStream = new ShapeStream({
   url: runStreamUrl,
   headers: {
     Authorization: `Bearer ${accessToken}`,
-    "x-trigger-electric-version": "1.0.0-beta.1",
-    "x-trigger-api-version": "2024-11-28",
+    "x-trigger-electric-version": this.electricVersion ?? "1.0.0-beta.1",
+    "x-trigger-api-version": this.apiVersion ?? "2024-11-28",
   },
   signal: runAbortController.signal,
 });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba97cb0 and 136085f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/react-hooks/package.json (1 hunks)
  • packages/react-hooks/src/hooks/useTriggerChat.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-hooks/package.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: For Realtime subscriptions or React hooks, provide a Public Access Token and scope it appropriately (e.g., via TriggerAuthContext)
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata API (metadata.current/get/set/append/stream, etc.) only inside run functions or lifecycle hooks
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata API (metadata.current/get/set/append/stream, etc.) only inside run functions or lifecycle hooks

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Define tasks using task({ id, run, ... }) with a unique id per project

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schemaTask({ schema, run, ... }) to validate payloads when input validation is required

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Do not use client.defineJob or any deprecated v2 patterns (e.g., eventTrigger) when defining tasks

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to trigger.config.ts : Configure global task lifecycle hooks (onStart/onSuccess/onFailure) only within trigger.config.ts if needed, not within arbitrary files

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use triggerAndWait() only from within a task context (not from generic app code) and handle result.ok or use unwrap() with error handling

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schedules.task(...) for scheduled (cron) tasks; do not implement schedules as plain task() with external cron logic

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : For idempotent child-task invocations, create and pass idempotencyKey (and optional TTL) when calling trigger()/batchTrigger() from tasks

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When triggering a task multiple times in a loop from inside another task, use batchTrigger()/batchTriggerAndWait() instead of per-item trigger() calls

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-07-18T17:49:24.468Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T17:49:24.468Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : We use zod a lot in packages/core and in the webapp

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: For Realtime subscriptions or React hooks, provide a Public Access Token and scope it appropriately (e.g., via TriggerAuthContext)

Applied to files:

  • packages/react-hooks/src/hooks/useTriggerChat.ts
🧬 Code graph analysis (1)
packages/react-hooks/src/hooks/useTriggerChat.ts (1)
packages/core/src/v3/apiClientManager/index.ts (2)
  • accessToken (37-45)
  • baseURL (32-35)
🔇 Additional comments (7)
packages/react-hooks/src/hooks/useTriggerChat.ts (7)

1-20: LGTM: Clean imports and sensible defaults.

The imports are well-organized and the default constants provide good fallback values for common use cases.


33-132: LGTM: Comprehensive interface with excellent documentation.

The inline JSDoc examples are appropriately placed and very helpful for users. Documenting critical setup requirements directly in the interface definition is a best practice, especially given the complexity of the three-file setup (task, server action, client component).


159-337: LGTM: Well-structured transport implementation.

The class properly manages resources with cleanup handlers, handles errors appropriately, and provides both initial connection and reconnection capabilities. The token resolution logic correctly prioritizes stored tokens while providing fallback options.


339-361: LGTM: Pragmatic metadata parsing.

The silent error handling and type casting are acceptable here given that subscribeToDataStreams defensively validates the metadata structure. This approach matches existing patterns in the codebase and avoids adding zod as a dependency.


401-411: LGTM: Promise rejection now properly handled.

The addition of .catch() successfully addresses the previous review concern about unhandled promise rejections. The error is properly propagated to the controller and the run is aborted to ensure clean resource cleanup.


415-491: LGTM: Robust SSE streaming implementation.

The stream handling is well-implemented with appropriate error handling. The decision to continue processing despite individual parse errors adds resilience, and the intentional reliance on run.finishedAt for cleanup (rather than stream end) ensures proper lifecycle management.


530-620: LGTM: Excellent documentation and clean implementation.

The extensive three-file example is exactly what users need to understand the setup requirements. The documentation placement is ideal—comprehensive examples at the public API level guide developers through the entire integration. The hook implementation properly validates dependencies and delegates to useChat.

Comment on lines +493 to +524
function checkRequiredPeerDependencies() {
const requiredPackages = [
"@ai-sdk/react",
"ai",
"@electric-sql/client",
"eventsource-parser",
];

const missing = requiredPackages.filter((pkg) => {
try {
require.resolve(pkg);
return false;
} catch {
return true;
}
});

if (!missing.length) return;

const packages = missing.join(" ");

throw new Error(
`useTriggerChat requires the following packages:\n${missing
.map((pkg) => ` - ${pkg}`)
.join("\n")}\n\n` +
`Install them with:\n` +
` npm install ${packages}\n` +
` yarn add ${packages}\n` +
` pnpm add ${packages}\n` +
` bun add ${packages}`,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace Node.js-specific require.resolve with isomorphic approach.

The require.resolve() call violates the coding guideline to prefer isomorphic code over Node.js-specific APIs. Since this runs in a client-side hook ("use client"), it may fail in browser-only environments or bundlers without Node.js polyfills.

Consider one of these approaches:

Option 1: Remove runtime check entirely (recommended)

-function checkRequiredPeerDependencies() {
-  const requiredPackages = [
-    "@ai-sdk/react",
-    "ai",
-    "@electric-sql/client",
-    "eventsource-parser",
-  ];
-
-  const missing = requiredPackages.filter((pkg) => {
-    try {
-      require.resolve(pkg);
-      return false;
-    } catch {
-      return true;
-    }
-  });
-
-  if (!missing.length) return;
-
-  const packages = missing.join(" ");
-
-  throw new Error(
-    `useTriggerChat requires the following packages:\n${missing
-      .map((pkg) => `  - ${pkg}`)
-      .join("\n")}\n\n` +
-      `Install them with:\n` +
-      `  npm install ${packages}\n` +
-      `  yarn add ${packages}\n` +
-      `  pnpm add ${packages}\n` +
-      `  bun add ${packages}`,
-  );
-}

Rely on TypeScript types and package.json peerDependencies for compile-time checks instead.

Option 2: Check for actual exports (if runtime check is needed)

 function checkRequiredPeerDependencies() {
-  const requiredPackages = [
-    "@ai-sdk/react",
-    "ai",
-    "@electric-sql/client",
-    "eventsource-parser",
-  ];
-
-  const missing = requiredPackages.filter((pkg) => {
-    try {
-      require.resolve(pkg);
-      return false;
-    } catch {
-      return true;
-    }
-  });
+  const missing: string[] = [];
+  
+  try {
+    if (typeof ShapeStream === 'undefined') missing.push('@electric-sql/client');
+  } catch {}
+  
+  try {
+    if (typeof EventSourceParserStream === 'undefined') missing.push('eventsource-parser');
+  } catch {}
+  
+  try {
+    if (typeof useChat === 'undefined') missing.push('@ai-sdk/react');
+  } catch {}
 
   if (!missing.length) return;
 
   const packages = missing.join(" ");
 
   throw new Error(
     `useTriggerChat requires the following packages:\n${missing
       .map((pkg) => `  - ${pkg}`)
       .join("\n")}\n\n` +
       `Install them with:\n` +
       `  npm install ${packages}\n` +
       `  yarn add ${packages}\n` +
       `  pnpm add ${packages}\n` +
       `  bun add ${packages}`,
   );
 }

As per coding guidelines.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/react-hooks/src/hooks/useTriggerChat.ts around lines 493 to 524, the
runtime check using Node's require.resolve is not isomorphic and can break in
browser/bundler environments; remove the runtime package-resolution logic and
either delete this function entirely or replace it with a no-op that documents
reliance on TypeScript types and package.json peerDependencies for compile-time
validation; if you must keep a runtime guard, instead perform safe dynamic
import checks only in non-browser/Node environments behind a typeof window ===
"undefined" guard and handle failures gracefully, but the preferred fix is to
remove the runtime check and any calls to it so the hook remains client-safe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: AI SDK ChatTransport

1 participant